package com.yanmaohu.nikita.parser;

import com.yanmaohu.nikita.NikitaException;
import com.yanmaohu.nikita.data.expression.AssignmentExpression;
import com.yanmaohu.nikita.data.expression.Expression;
import com.yanmaohu.nikita.data.expression.InvocationExpression;
import com.yanmaohu.nikita.data.expression.NameExpression;
import com.yanmaohu.nikita.data.function.FunctionBody;
import com.yanmaohu.nikita.data.function.Interval;
import com.yanmaohu.nikita.data.function.LocalVariableModifier;
import com.yanmaohu.nikita.data.function.ParameterList;
import com.yanmaohu.nikita.data.function.statement.*;
import com.yanmaohu.nikita.data.structure.StructureBody;
import com.yanmaohu.nikita.scanner.SourceCode;
import com.yanmaohu.nikita.scanner.Token;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

/**
 * @author Bright Lee
 */
public final class StatementParser {

    private Parser parser;
    private ExpressionParser expressionParser;
    private ModifierParser modifierParser;
    private MemberParser memberParser;

    protected StatementParser(Parser parser) {
        this.parser = parser;
    }

    public void setExpressionParser(ExpressionParser expressionParser) {
        this.expressionParser = expressionParser;
    }

    public void setModifierParser(ModifierParser modifierParser) {
        this.modifierParser = modifierParser;
    }

    public void setStructureMemberParser(MemberParser memberParser) {
        this.memberParser = memberParser;
    }

    private File getFile() {
        return parser.getFile();
    }

    private SourceCode getCode() {
        return parser.getCode();
    }

    protected Statement tryParseStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isId()) {
            int markedIndex1 = mark();
            Token token1 = scan();
            if (token1.isLeftBrc()) {
                reset(markedIndex1);
                StructureBody structureBody = memberParser.parseStructureMemberList();
                NameExpression structureName = new NameExpression(token);
                return new StructureStatement(structureName, structureBody);
            }
        }
        reset(markedIndex);
        BreakStatement breakStatement = tryParseBrStatement();
        if (breakStatement != null) {
            return breakStatement;
        }
        ContinueStatement continueStatement = tryParseContStatement();
        if (continueStatement != null) {
            return continueStatement;
        }
        GotoStatement gotoStatement = tryParseGotoStatement();
        if (gotoStatement != null) {
            return gotoStatement;
        }
        ReturnStatement returnStatement = tryParseReturnStatement();
        if (returnStatement != null) {
            return returnStatement;
        }
        ForStatement forStatement = tryParseForStatement();
        if (forStatement != null) {
            return forStatement;
        }
        WhileStatement whileStmt = tryParseWhileStatement();
        if (whileStmt != null) {
            return whileStmt;
        }
        DoWhileStatement doWhileStatement = tryParseDoWhileStatement();
        if (doWhileStatement != null) {
            return doWhileStatement;
        }
        CaseStatement caseStatement = tryParseCaseStatement();
        if (caseStatement != null) {
            return caseStatement;
        }
        LabelStatement labelStatement = tryParseLabelStatement();
        if (labelStatement != null) {
            return labelStatement;
        }
        FunctionStatement functionStatement = tryParseFunctionStatement();
        if (functionStatement != null) {
            return functionStatement;
        }
        InvocationStatement invocationStatement = tryParseInvocationStatement();
        if (invocationStatement != null) {
            return invocationStatement;
        }
        AssignmentStatement assignmentStatement = tryParseAssignmentStatement();
        if (assignmentStatement != null) {
            return assignmentStatement;
        }
        return null;
    }

    private AssignmentStatement tryParseAssignmentStatement() {
        int markedIndex = mark();
        Expression expression = expressionParser.tryParseExpression();
        if (expression == null) {
            return null;
        }
        if (!(expression instanceof AssignmentExpression)) {
            reset(markedIndex);
            return null;
        }
        AssignmentExpression assignmentExpression = (AssignmentExpression) expression;
        Token token1 = scan();
        if (!token1.isSemicolon()) {
            throw new NikitaException(getCode(), token1, "This must be a ;.");
        }
        return new AssignmentStatement(
                assignmentExpression.getBeginIndex(),
                assignmentExpression.getEndIndex(),
                assignmentExpression
        );
    }

    private InvocationStatement tryParseInvocationStatement() {
        int markedIndex = mark();
        Expression expression = expressionParser.tryParseExpression();
        if (expression == null) {
            return null;
        }
        if (!(expression instanceof InvocationExpression)) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isSemicolon()) {
            throw new NikitaException(getCode(), token1, "This must be a ;.");
        }
        return new InvocationStatement(expression.getBeginIndex(), token1.getEndIndex(), (InvocationExpression) expression);
    }

    private FunctionStatement tryParseFunctionStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isId()) {
            reset(markedIndex);
            return null;
        }
        NameExpression functionName = new NameExpression(token);
        ParameterList parameterList = parser.tryParseParameterList();
        if (parameterList != null) {
            FunctionBody functionBody = tryParseStatementList();
            if (functionBody == null) {
                if (parameterList.size() > 1) {
                    Token token2 = scan();
                    throw new NikitaException(getCode(), token2, "This must be a function body.");
                }
            } else {
                return new FunctionStatement(functionName, parameterList, functionBody);
            }
        }
        reset(markedIndex);
        return null;
    }

    private BreakStatement tryParseBrStatement() {
        mark();
        Token token = scan();
        if (!token.isBreak()) {
            reset();
            return null;
        }
        int beginIndex = token.getBeginIndex();
        NameExpression lbl = null;
        mark();
        token = scan();
        if (!token.isId()) {
            reset();
        } else {
            lbl = new NameExpression(token);
        }
        token = scan();
        if (!token.isSemicolon()) {
            throw new NikitaException(getCode(), token, "This must be a ;.");
        }
        return new BreakStatement(beginIndex, token.getEndIndex(), lbl);
    }

    private ContinueStatement tryParseContStatement() {
        mark();
        Token token = scan();
        if (!token.isContinue()) {
            reset();
            return null;
        }
        int beginIndex = token.getBeginIndex();
        NameExpression label = expressionParser.tryParseNameExpression();
        token = scan();
        if (!token.isSemicolon()) {
            throw new NikitaException(getCode(), token, "This must be a ;.");
        }
        return new ContinueStatement(beginIndex, token.getEndIndex(), label);
    }

    private GotoStatement tryParseGotoStatement() {
        mark();
        Token token = scan();
        if (!token.isGoto()) {
            reset();
            return null;
        }
        int beginIndex = token.getBeginIndex();
        NameExpression label = expressionParser.tryParseNameExpression();
        token = scan();
        if (!token.isSemicolon()) {
            throw new NikitaException(getCode(), token, "This must be a ;.");
        }
        return new GotoStatement(beginIndex, token.getEndIndex(), label);
    }

    private ReturnStatement tryParseReturnStatement() {
        mark();
        Token token = scan();
        if (!token.isReturn()) {
            reset();
            return null;
        }
        int beginIndex = token.getBeginIndex();
        Expression returnValue;
        returnValue = expressionParser.tryParseExpression();
        token = scan();
        if (!token.isSemicolon()) {
            throw new NikitaException(getCode(), token, "This must be a ;.");
        }
        return new ReturnStatement(beginIndex, token.getEndIndex(), returnValue);
    }

    private ForStatement tryParseForStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isFor()) {
            reset(markedIndex);
            return null;
        }
        final int beginIndex = token.getBeginIndex();
        Token token1 = scan();
        if (!token1.isLeftPar()) {
            throw new NikitaException(getCode(), token1, "This must be a (.");
        }
        LocalVariableModifier aModifier = modifierParser.tryParseLocalVariableModifier();
        Expression a = expressionParser.parseExpression();
        Token token2 = scan();
        if (token2.isComma()) {
            LocalVariableModifier indexModifier = modifierParser.tryParseLocalVariableModifier();
            Expression index = expressionParser.parseExpression();
            Token token3 = scan();
            if (!token3.isColon()) {
                throw new NikitaException(getCode(), token3, "This must be a :.");
            }
            Interval interval = tryParseInterval();
            if (interval != null) {
                Token token4 = scan();
                if (!token4.isRightPar()) {
                    throw new NikitaException(getCode(), token4, "This must be a ).");
                }
                FunctionBody functionBody = parseStatementList();
                return new ForStatement(
                        beginIndex,
                        functionBody.getEndIndex(),
                        aModifier,
                        a,
                        indexModifier,
                        index,
                        interval,
                        functionBody
                );
            }
            Expression collection = expressionParser.parseExpression();
            Token token4 = scan();
            if (!token4.isRightPar()) {
                throw new NikitaException(getCode(), token4, "This must be a ).");
            }
            FunctionBody functionBody = parseStatementList();
            return new ForStatement(
                    beginIndex,
                    functionBody.getEndIndex(),
                    aModifier,
                    a,
                    indexModifier,
                    index,
                    collection,
                    functionBody
            );
        } else if (token2.isColon()) {
            Interval interval = tryParseInterval();
            if (interval != null) {
                Token token3 = scan();
                if (!token3.isRightPar()) {
                    throw new NikitaException(getCode(), token3, "This must be a ).");
                }
                FunctionBody functionBody = parseStatementList();
                return new ForStatement(
                        beginIndex,
                        functionBody.getEndIndex(),
                        aModifier,
                        a,
                        interval,
                        functionBody
                );
            }
            Expression collection = expressionParser.parseExpression();
            Token token3 = scan();
            if (!token3.isRightPar()) {
                throw new NikitaException(getCode(), token3, "This must be a ).");
            }
            FunctionBody functionBody = parseStatementList();
            return new ForStatement(
                    beginIndex,
                    functionBody.getEndIndex(),
                    aModifier,
                    a,
                    collection,
                    functionBody
            );
        }
        if (!token2.isSemicolon()) {
            throw new NikitaException(getCode(), token2, "This must be a ;.");
        }
        Expression b = expressionParser.tryParseExpression();
        Token token3 = scan();
        if (!token3.isSemicolon()) {
            throw new NikitaException(getCode(), token3, "This must be a ;.");
        }
        Expression c = expressionParser.tryParseExpression();
        Token token4 = scan();
        if (!token4.isRightPar()) {
            throw new NikitaException(getCode(), token4, "This must be a ).");
        }
        FunctionBody functionBody = parseStatementList();
        return new ForStatement(beginIndex, token.getEndIndex(), aModifier, a, b, c, functionBody);
    }

    private Interval tryParseInterval() {
        final int markedIndex1 = mark();
        final Token token1 = scan();
        if (!token1.isLeftPar() && !token1.isLeftBrk()) {
            reset(markedIndex1);
            return null;
        }
        int intervalBeginIndex = token1.getBeginIndex();
        boolean lowerLimitClosed;
        if (token1.isLeftPar()) {
            lowerLimitClosed = false;
        } else {
            lowerLimitClosed = true;
        }
        Expression lowerLimit = expressionParser.parseExpression();
        final Token token2 = scan();
        if (!token2.isComma()) {
            reset(markedIndex1);
            return null;
        } else {
            int intervalEndIndex;
            boolean upperLimitClosed;
            Expression upperLimit = expressionParser.parseExpression();
            Token token3 = scan();
            if (token3.isRightPar()) {
                intervalEndIndex = token1.getEndIndex();
                upperLimitClosed = false;
            } else if (token3.isRightBrk()) {
                intervalEndIndex = token1.getEndIndex();
                upperLimitClosed = true;
            } else {
                throw new NikitaException(getCode(), token1, "This must be a ) or ].");
            }
            return new Interval(
                    intervalBeginIndex,
                    intervalEndIndex,
                    lowerLimitClosed,
                    lowerLimit,
                    upperLimitClosed,
                    upperLimit);
        }
    }

    protected FunctionBody parseStatementList() {
        FunctionBody functionBody = tryParseStatementList();
        if (functionBody != null) {
            return functionBody;
        }
        Token token = scan();
        throw new NikitaException(getCode(), token, "Cannot resolve to statement list.");
    }

    protected FunctionBody tryParseStatementList() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isLeftBrc()) {
            reset(markedIndex);
            return null;
        }
        int beginIndex = token.getBeginIndex();
        List<Statement> statementList = null;
        while (true) {
            Statement statement = tryParseStatement();
            if (statement == null) {
                break;
            }
            if (statementList == null) {
                statementList = new ArrayList<>();
            }
            statementList.add(statement);
        }
        token = scan();
        if (!token.isRightBrc()) {
            throw new NikitaException(getCode(), token, "This must be a }.");
        }
        int endIndex = token.getEndIndex();
        return new FunctionBody(beginIndex, endIndex, statementList);
    }

    private DoWhileStatement tryParseDoWhileStatement() {
        mark();
        Token token = scan();
        if (!token.isDo()) {
            reset();
            return null;
        }
        int beginIndex = token.getBeginIndex();
        FunctionBody functionBody = parseStatementList();
        mark();
        token = scan();
        reset();
        if (!token.isWhile()) {
            throw new NikitaException(getCode(), token, "This must be a while.");
        }
        mark();
        token = scan();
        if (!token.isLeftPar()) {
            throw new NikitaException(getCode(), token, "This must be a (.");
        }
        Expression condition = expressionParser.parseExpression();
        mark();
        token = scan();
        if (!token.isRightPar()) {
            throw new NikitaException(getCode(), token, "This must be a ).");
        }
        token = scan();
        if (!token.isSemicolon()) {
            throw new NikitaException(getCode(), token, "This must be a ;.");
        }
        return new DoWhileStatement(beginIndex, token.getEndIndex(), functionBody, condition);
    }

    private WhileStatement tryParseWhileStatement() {
        mark();
        Token token = scan();
        if (!token.isWhile()) {
            reset();
            return null;
        }
        int beginIndex = token.getBeginIndex();
        mark();
        token = scan();
        if (!token.isLeftPar()) {
            throw new NikitaException(getCode(), token, "This must be a (.");
        }
        Expression condition = expressionParser.parseExpression();
        mark();
        token = scan();
        if (!token.isRightPar()) {
            throw new NikitaException(getCode(), token, "This must be a ).");
        }
        FunctionBody functionBody = parseStatementList();
        return new WhileStatement(beginIndex, functionBody.getEndIndex(), condition, functionBody);
    }

    private CaseStatement tryParseCaseStatement() {
        mark();
        Token token = scan();
        if (!token.isCase()) {
            reset();
            return null;
        }
        final int beginIndex = token.getBeginIndex();
        List<Expression> case_ = null;
        token = scan();
        if (token.isLeftPar()) {
            while (true) {
                Expression expression = expressionParser.parseExpression();
                if (case_ == null) {
                    case_ = new ArrayList<>();
                }
                case_.add(expression);
                mark();
                token = scan();
                if (token.isComma()) {
                    continue;
                }
                break;
            }
            if (!token.isRightPar()) {
                throw new NikitaException(getCode(), token, "This must be a ).");
            }
            token = scan();
        }
        if (!token.isLeftBrc()) {
            throw new NikitaException(getCode(), token, "This must be a {.");
        }
        List<List<Expression>> whenHeadList = null;
        List<FunctionBody> whenFunctionBody = null;
        while (true) {
            mark();
            token = scan();
            if (!token.isWhen()) {
                break;
            }
            token = scan();
            if (!token.isLeftPar()) {
                throw new NikitaException(getCode(), token, "This must be a (.");
            }
            List<Expression> whenHead = new ArrayList<>();
            do {
                Expression expression = expressionParser.parseExpression();
                whenHead.add(expression);
                mark();
                token = scan();
            } while (token.isComma());
            if (!token.isRightPar()) {
                throw new NikitaException(getCode(), token, "This must be a ).");
            }
            FunctionBody whenBody = parseStatementList();
            if (whenHeadList == null) {
                whenHeadList = new ArrayList<>();
                whenFunctionBody = new ArrayList<>();
            }
            whenHeadList.add(whenHead);
            whenFunctionBody.add(whenBody);
        }
        FunctionBody else_;
        if (token.isElse()) {
            if (whenHeadList == null) {
                throw new NikitaException(getCode(), token, "A case statement cannot have only else clause.");
            }
            else_ = parseStatementList();
        } else {
            reset();
            else_ = null;
        }
        token = scan();
        if (!token.isRightBrc()) {
            throw new NikitaException(getCode(), token, "This must be a }.");
        }
        final int endIndex = token.getEndIndex();
        return new CaseStatement(beginIndex, endIndex, case_, whenHeadList, whenFunctionBody, else_);
    }

    private LabelStatement tryParseLabelStatement() {
        int marked = mark();
        Token token = scan();
        if (!token.isId()) {
            reset(marked);
            return null;
        }
        NameExpression nameExp = new NameExpression(token);
        token = scan();
        if (!token.isColon()) {
            reset(marked);
            return null;
        }
        mark();
        token = scan();
        reset();
        ForStatement forStatement = tryParseForStatement();
        if (forStatement != null) {
            return new LabelStatement(nameExp, forStatement);
        }
        WhileStatement whileStmt = tryParseWhileStatement();
        if (whileStmt != null) {
            return new LabelStatement(nameExp, whileStmt);
        }
        DoWhileStatement doWhileStatement = tryParseDoWhileStatement();
        if (doWhileStatement != null) {
            return new LabelStatement(nameExp, doWhileStatement);
        }
        throw new NikitaException(getCode(), token, "This must be a loop statement.");
    }

    int mark() {
        return parser.mark();
    }

    Token scan() {
        return parser.scan();
    }

    void reset(int markedIndex) {
        parser.reset(markedIndex);
    }

    void reset() {
        parser.reset();
    }

}
